// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.media.router.cast.remoting;

import android.support.annotation.Nullable;
import android.support.v7.media.MediaRouter;

import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.media.router.ChromeMediaRouter;
import org.chromium.chrome.browser.media.router.FlingingController;
import org.chromium.chrome.browser.media.router.MediaRoute;
import org.chromium.chrome.browser.media.router.MediaRouteManager;
import org.chromium.chrome.browser.media.router.MediaRouteProvider;
import org.chromium.chrome.browser.media.router.MediaSink;
import org.chromium.chrome.browser.media.router.MediaSource;
import org.chromium.chrome.browser.media.router.cast.BaseMediaRouteProvider;
import org.chromium.chrome.browser.media.router.cast.CastSession;
import org.chromium.chrome.browser.media.router.cast.ChromeCastSessionManager;
import org.chromium.chrome.browser.media.router.cast.CreateRouteRequest;

/**
 * A {@link MediaRouteProvider} implementation for media remote playback.
 */
// Migrated to CafRemotingMediaRouteProvider. See https://crbug.com/711860.
public class RemotingMediaRouteProvider extends BaseMediaRouteProvider {
    private static final String TAG = "MediaRemoting";

    private int mPendingNativeRequestId;
    private MediaRoute mPendingMediaRoute;

    /**
     * @return Initialized {@link RemotingMediaRouteProvider} object.
     */
    public static RemotingMediaRouteProvider create(MediaRouteManager manager) {
        return new RemotingMediaRouteProvider(ChromeMediaRouter.getAndroidMediaRouter(), manager);
    }

    @Override
    protected MediaSource getSourceFromId(String sourceId) {
        return RemotingMediaSource.from(sourceId);
    }

    @Override
    protected ChromeCastSessionManager.CastSessionLaunchRequest createSessionLaunchRequest(
            MediaSource source, MediaSink sink, String presentationId, String origin, int tabId,
            boolean isIncognito, int nativeRequestId) {
        return new CreateRouteRequest(source, sink, presentationId, origin, tabId, isIncognito,
                nativeRequestId, this, CreateRouteRequest.RequestedCastSessionType.REMOTE, null);
    }

    @Override
    public void joinRoute(
            String sourceId, String presentationId, String origin, int tabId, int nativeRequestId) {
        mManager.onRouteRequestError(
                "Remote playback doesn't support joining routes", nativeRequestId);
    }

    @Override
    public void closeRoute(String routeId) {
        MediaRoute route = mRoutes.get(routeId);
        if (route == null) return;

        if (mSession == null) {
            mRoutes.remove(routeId);
            mManager.onRouteTerminated(routeId);
            return;
        }

        ChromeCastSessionManager.get().stopApplication();
    }

    @Override
    public void detachRoute(String routeId) {
        mRoutes.remove(routeId);
    }

    @Override
    public void sendStringMessage(String routeId, String message) {
        Log.e(TAG, "Remote playback does not support sending messages");
    }

    @VisibleForTesting
    RemotingMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) {
        super(androidMediaRouter, manager);
    }

    @Override
    public void onSessionEnded() {
        if (mSession == null) return;

        for (String routeId : mRoutes.keySet()) mManager.onRouteTerminated(routeId);
        mRoutes.clear();

        mSession = null;

        if (mAndroidMediaRouter != null) {
            mAndroidMediaRouter.selectRoute(mAndroidMediaRouter.getDefaultRoute());
        }
    }

    @Override
    public void onSessionStarting(ChromeCastSessionManager.CastSessionLaunchRequest launchRequest) {
        CreateRouteRequest request = (CreateRouteRequest) launchRequest;
        MediaSink sink = request.getSink();
        MediaSource source = request.getSource();

        // Calling mManager.onRouteCreated() too early causes some issues. If we call it here
        // directly, getMediaController() might be called before onSessionStarted(), which causes
        // the FlingingRenderer's creation to fail. Instead, save the route and request ID, and only
        // signal the route as having been created when onSessionStarted() is called.
        mPendingMediaRoute =
                new MediaRoute(sink.getId(), source.getSourceId(), request.getPresentationId());
        mPendingNativeRequestId = request.getNativeRequestId();
    }

    private void clearPendingRoute() {
        mPendingMediaRoute = null;
        mPendingNativeRequestId = 0;
    }

    @Override
    public void onSessionStarted(CastSession session) {
        super.onSessionStarted(session);

        // Continued from onSessionStarting()
        mRoutes.put(mPendingMediaRoute.id, mPendingMediaRoute);
        mManager.onRouteCreated(mPendingMediaRoute.id, mPendingMediaRoute.sinkId,
                mPendingNativeRequestId, this, true);

        clearPendingRoute();
    }

    @Override
    public void onSessionStartFailed() {
        super.onSessionStartFailed();

        mManager.onRouteRequestError(
                "Failure to start RemotingCastSession", mPendingNativeRequestId);

        clearPendingRoute();
    };

    @Override
    @Nullable
    public FlingingController getFlingingController(String routeId) {
        // We cannot return a FlingingController if we don't have a session.
        if (mSession == null) return null;

        // Don't return controllers for stale routes.
        if (!mRoutes.containsKey(routeId)) return null;

        // RemotePlayback does not support joining routes, which means we only
        // have a single route active at a time. If we have a a valid CastSession
        // and the route ID is current, this means that the given |mSession|
        // corresponds to the route ID, and it is ok to return the FlingingController.
        return mSession.getFlingingController();
    }
}
